package aceim.app.view.page.contactlist; import java.io.File; import java.lang.reflect.Constructor; import java.util.Arrays; import java.util.Calendar; import java.util.List; import aceim.api.dataentity.ActionFeature; import aceim.api.dataentity.Buddy; import aceim.api.dataentity.ConnectionState; import aceim.api.dataentity.FileMessage; import aceim.api.dataentity.InputFormFeature; import aceim.api.dataentity.ListFeature; import aceim.api.dataentity.Message; import aceim.api.dataentity.MessageAckState; import aceim.api.dataentity.OnlineInfo; import aceim.api.dataentity.ProtocolServiceFeature; import aceim.api.dataentity.ProtocolServiceFeatureTarget; import aceim.api.dataentity.ToggleFeature; import aceim.api.service.ApiConstants; import aceim.api.utils.Logger; import aceim.api.utils.Logger.LoggerLevel; import aceim.api.utils.Utils; import aceim.app.AceImException; import aceim.app.MainActivity; import aceim.app.R; import aceim.app.dataentity.Account; import aceim.app.dataentity.AccountOptionKeys; import aceim.app.dataentity.ActivityResult; import aceim.app.dataentity.ProtocolResources; import aceim.app.dataentity.listeners.IHasAccount; import aceim.app.dataentity.listeners.IHasFilePicker; import aceim.app.dataentity.listeners.IHasMessages; import aceim.app.service.ICoreService; import aceim.app.service.ServiceUtils; import aceim.app.utils.DialogUtils; import aceim.app.utils.ViewUtils; import aceim.app.view.page.Page; import aceim.app.view.page.chat.Chat; import android.app.Activity; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.provider.MediaStore; import android.text.TextUtils; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; import com.androidquery.AQuery; public abstract class ContactList extends Page implements IHasAccount, IHasMessages, IHasFilePicker { private static final String SAVE_PARAM_URI = "SaveParamUri"; protected final Account mAccount; protected final ProtocolResources mProtocolResources; private final ContactListUpdater mUpdater; private Uri mAwaitingUri = null; private final OnLongClickListener mUpdateIconListener = new OnLongClickListener() { @Override public boolean onLongClick(View v) { if (mAccount.getConnectionState() != ConnectionState.CONNECTED || !mAccount.getOnlineInfo().getFeatures().getBoolean(ApiConstants.FEATURE_ACCOUNT_MANAGEMENT, false)) { return true; } Context context = getMainActivity(); final Dialog dialog = new Dialog(context); dialog.setTitle(R.string.setting_account_icon); ListView list = new ListView(context); String[] buttonList; if (ViewUtils.hasIcon(context, mAccount.getFilename())) { buttonList = new String[]{context.getString(R.string.take_photo), context.getString(R.string.choose_photo), context.getString(R.string.remove_photo)}; } else { buttonList = new String[]{context.getString(R.string.take_photo), context.getString(R.string.choose_photo)}; } list.setAdapter(new ArrayAdapter<String>(context, android.R.layout.simple_list_item_1, android.R.id.text1, buttonList)); list.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ContactList cl = ContactList.this; Intent intent = null; switch(position) { case 0: intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); mAwaitingUri = Uri.fromFile(Utils.createLocalFileForReceiving(android.text.format.DateFormat.getLongDateFormat(getMainActivity()).format(Calendar.getInstance().getTime()) + ".jpg", null, System.currentTimeMillis())); intent.putExtra(MediaStore.EXTRA_OUTPUT, mAwaitingUri); break; case 1: intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/*"); break; case 2: try { getMainActivity().getCoreService().removeAccountPhoto(mAccount.getServiceId()); } catch (RemoteException e) { getMainActivity().onRemoteException(e); } dialog.dismiss(); return; } int requestCode = ServiceUtils.getRequestCodeForActivity(cl.getAccount().getAccountId().hashCode()); try { cl.getMainActivity().startActivityForResult(intent, requestCode); } catch (Exception e){ Logger.log(e); ViewUtils.showAlertToast(getMainActivity(), android.R.drawable.ic_dialog_alert, R.string.no_app_for_picking_file_found, null); } dialog.dismiss(); } }); dialog.addContentView(list, new android.view.ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); dialog.setCancelable(true); DialogUtils.showBrandedDialog(dialog); return true; } }; protected ContactList(Account mAccount, ProtocolResources mProtocolResources, Resources applicationResources) { this.mAccount = mAccount; this.mProtocolResources = mProtocolResources; this.mUpdater = new ContactListUpdater(mAccount.getServiceId(), applicationResources, getContactListAdapterClassName(), this.mProtocolResources); //this.setHasOptionsMenu(true); } @SuppressWarnings("unchecked") public static ContactList createContactListPage(MainActivity activity, Account account) { if (account == null) { return null; } ProtocolResources resources = activity.getProtocolResourcesForAccount(account); if (resources == null) { Logger.log("Cannot find resources with id #" + account.getProtocolServicePackageName() + " for account #" + account.getProtocolUid(), LoggerLevel.WARNING); return null; } Resources appResources = activity.getResources(); try { // let's try to resolve contact list class by it's name String contactListType = activity .getSharedPreferences(account.getAccountId(), 0) .getString(AccountOptionKeys.CONTACT_LIST_TYPE.name(), activity.getString(R.string.default_contact_list_type)); Class<ContactList> cls = (Class<ContactList>) Class.forName(contactListType); Constructor<ContactList> constructor = cls.getConstructor(Account.class, ProtocolResources.class, Resources.class); ContactList contactList = constructor.newInstance(account, resources, appResources); return contactList; } catch (NullPointerException e1) { Logger.log("No contact list type set", LoggerLevel.INFO); } catch (ClassNotFoundException e1) { Logger.log("Unknown contact list type " + e1.getMessage(), LoggerLevel.WTF); } catch (Exception e1) { Logger.log(e1); } return new GridContactList(account, resources, appResources); } @Override public Account getAccount() { return mAccount; } @Override public void onOnlineInfoChanged(OnlineInfo info) { if (mAccount.getOnlineInfo() != info && mAccount.getServiceId() == info.getServiceId()) { mAccount.getOnlineInfo().merge(info); } } @Override public void onConnectionStateChanged(ConnectionState connState, int extraParameter){ if (mAccount.getConnectionState() != connState) { mAccount.setConnectionState(connState); if (mAccount.getConnectionState() != ConnectionState.CONNECTED) { for (Buddy b : mAccount.getBuddyList()) { ViewUtils.resetFeaturesForOffline(b.getOnlineInfo(), mProtocolResources, true); } } } if (mAccount.getConnectionState() == ConnectionState.DISCONNECTED) { onContactListUpdated(mAccount); } } @Override public View createView(LayoutInflater inflater, ViewGroup group, Bundle saved) { View view = onCreateContactListView(inflater, group, saved); onAccountIcon(mAccount.getServiceId()); onOnlineInfoChanged(mAccount.getOnlineInfo()); onContactListUpdated(mAccount); new AQuery(view).id(R.id.image_icon).longClicked(mUpdateIconListener); recoverFromStoredData(saved); return view; } @Override public void onSaveInstanceState(Bundle saver){ saver.putParcelable(SAVE_PARAM_URI, mAwaitingUri); } @Override public Bundle getPageDataForStorage(){ Bundle saver = new Bundle(); onSaveInstanceState(saver); return saver; } private void recoverFromStoredData(Bundle bundle){ if (bundle == null) { return; } mAwaitingUri = bundle.getParcelable(SAVE_PARAM_URI); } @Override public void onContactListUpdated(Account account) { mUpdater.onContactListUpdated(account, getMainActivity()); } @Override public void onBuddyStateChanged(List<Buddy> buddies) { mUpdater.onBuddyStateChanged(buddies); mergeBuddies(buddies); } @Override public Buddy getBuddy(){ return null; } @Override public Buddy getBuddyWithParameters(byte serviceId, String protocolUid) { if (serviceId != mAccount.getServiceId()) { return null; } return mAccount.getBuddyByProtocolUid(protocolUid); } @Override public Drawable getIcon(Context context) { if (mAccount.getUnreadMessages() > 0) { return context.getResources().getDrawable(R.drawable.tab_message); } return ViewUtils.getAccountStatusIcon(context, mAccount, mProtocolResources); } @Override public String getTitle(Context context) { return mAccount.getSafeName(); } @Override public String getPageId() { return getPageIdForEntityWithId(ContactList.class, mAccount); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater){ super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.contact_list_menu, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); MenuItem connect = menu.findItem(R.id.menuitem_connect); if (mAccount.getConnectionState() == ConnectionState.DISCONNECTED) { connect.setIcon(R.drawable.ic_menu_login); connect.setTitle(R.string.connect); } else { connect.setIcon(R.drawable.ic_menu_logout); connect.setTitle(R.string.disconnect); } Resources r; try { r = mProtocolResources.getNativeResourcesForProtocol(null); } catch (AceImException e) { Logger.log(e); return; } Resources myRes = getMainActivity().getBaseContext().getResources(); Bundle accountFeatures = mAccount.getOnlineInfo().getFeatures(); if (mAccount.getConnectionState() != ConnectionState.CONNECTING) { for (ProtocolServiceFeature feature : mProtocolResources.getFeatures()) { if (feature == null || !feature.isEditable() || !feature.isAppliedToTarget(ProtocolServiceFeatureTarget.ACCOUNT)) { continue; } if (!feature.isAvailableOffline() && mAccount.getConnectionState() != ConnectionState.CONNECTED) { continue; } int featureMenuId = feature.getFeatureId().hashCode(); MenuItem item = menu.add(0, featureMenuId, 1, feature.getFeatureName()); if (feature instanceof ListFeature) { ListFeature lf = (ListFeature) feature; if (lf.getDrawables() != null && lf.getNames() != null) { Drawable icon; String title; byte value = accountFeatures.getByte(feature.getFeatureId(), (byte) -1); if (value > -1) { icon = r.getDrawable(lf.getDrawables()[value]); title = r.getString(lf.getNames()[value]); } else { icon = myRes.getDrawable(R.drawable.empty); title = lf.getFeatureName(); } item.setIcon(icon); if (feature.getFeatureId().equals(ApiConstants.FEATURE_XSTATUS) && !TextUtils.isEmpty(mAccount.getOnlineInfo().getXstatusName())) { item.setTitle(ViewUtils.getFormattedXStatus(mAccount.getOnlineInfo(), mAccount.getConnectionState(), getMainActivity(), mProtocolResources)); } else { item.setTitle(title); } } else { if (feature.getFeatureId().equals(ApiConstants.FEATURE_XSTATUS)) { if (TextUtils.isEmpty(mAccount.getOnlineInfo().getXstatusName())) { item.setTitle(lf.getFeatureName()); } else { item.setTitle(ViewUtils.getFormattedXStatus(mAccount.getOnlineInfo(), mAccount.getConnectionState(), getMainActivity(), mProtocolResources)); } } else { continue; } } } else if (feature instanceof InputFormFeature || feature instanceof ActionFeature) { if (feature.getIconId() != 0){ item.setIcon(r.getDrawable(feature.getIconId())); } } else if (feature instanceof ToggleFeature) { ToggleFeature tf = (ToggleFeature) feature; item.setIcon(tf.getValue() ? android.R.drawable.button_onoff_indicator_on : android.R.drawable.button_onoff_indicator_off); } } } MenuItem addGroup = menu.findItem(R.id.menuitem_add_group); if (addGroup != null) { addGroup.setVisible(mAccount.getConnectionState() == ConnectionState.CONNECTED && accountFeatures.getBoolean(ApiConstants.FEATURE_GROUP_MANAGEMENT, false)); } } @Override public boolean onOptionsItemSelected(MenuItem item) { MainActivity activity = getMainActivity(); ICoreService service = activity.getCoreService(); try { switch(item.getItemId()) { case R.id.menuitem_connect: if (mAccount.getConnectionState() == ConnectionState.DISCONNECTED) { service.connect(mAccount.getServiceId()); } else { service.disconnect(mAccount.getServiceId()); } break; case R.id.menuitem_exit: activity.exitApplication(); break; case R.id.menuitem_account: activity.openOptions(mAccount); break; case R.id.menuitem_accounts: List<Account> accounts = getMainActivity().getCoreService().getAccounts(true); Page.addAccountManagerPage(activity.getScreen(), accounts); break; case R.id.menuitem_prefs: activity.openOptions(null); break; case R.id.menuitem_add_group: DialogUtils.showAddOrRenameGroupDialog(null, mAccount, activity); break; case R.id.menuitem_about: Page.getAboutPage(activity.getScreen()); break; default: ProtocolServiceFeature feature = mProtocolResources.getFeature(item.getItemId()); if (feature instanceof ListFeature) { ListFeature lf = (ListFeature) feature; DialogUtils.showEditListFeatureDialog(getMainActivity(), mAccount.getOnlineInfo(), mProtocolResources, lf); } else if (feature instanceof InputFormFeature) { InputFormFeature iff = (InputFormFeature) feature; Page.getInputFormPage(getMainActivity(), iff, mAccount.getOnlineInfo(), mProtocolResources); } else if (feature instanceof ActionFeature) { activity.getCoreService().setFeature(feature.getFeatureId(), mAccount.getOnlineInfo()); } break; } } catch (RemoteException e) { activity.onRemoteException(e); } return false; } @Override public boolean onKeyDown(int i, KeyEvent event) { if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { //Here we need to minimize activity, so override default logic of choosing 1st page. return false; } return super.onKeyDown(i, event); } /* (non-Javadoc) * @see aceim.app.dataentity.listeners.IHasMessages#messageReceived(aceim.app.api.dataentity.Message) */ @Override public void onMessageReceived(Message message) { if (message instanceof FileMessage || !message.isIncoming()) { return; } Buddy b = mAccount.getBuddyByProtocolUid(message.getContactUid()); if (b == null) { Logger.log("No buddy found for " + message.getContactUid(), LoggerLevel.WARNING); return; } if (getMainActivity().isActivityVisible()) { Page selectedPage = getMainActivity().getScreen().getSelectedPage(); if (selectedPage.getPageId().equals(Page.getPageIdForEntityWithId(Chat.class, b))) { return; } } b.incrementUnread(); onBuddyStateChanged(Arrays.asList(b)); try { getMainActivity().getCoreService().notifyUnread(message, b); } catch (RemoteException e) { getMainActivity().onRemoteException(e); } } /* (non-Javadoc) * @see aceim.app.dataentity.listeners.IHasMessages#messageAckReceived(long, aceim.app.api.dataentity.MessageAckState) */ @Override public void onMessageAckReceived(long messageId, MessageAckState ack) {} /* (non-Javadoc) * @see aceim.app.dataentity.listeners.IHasMessages#hasMessagesOfBuddy(byte, java.lang.String) */ @Override public boolean hasMessagesOfBuddy(byte serviceId, String buddyProtocolUid) { return mAccount.getServiceId() == serviceId; } /* (non-Javadoc) * @see aceim.app.dataentity.listeners.IHasBuddy#onBuddyIcon(byte, java.lang.String) */ /*@Override public void onBuddyIcon(byte serviceId, String protocolUid) { mUpdater.onBuddyIcon(serviceId, protocolUid); }*/ /* (non-Javadoc) * @see aceim.app.dataentity.listeners.IHasAccount#onAccountIcon(byte) */ @Override public void onAccountIcon(byte serviceId) {} @Override public boolean hasThisBuddy(byte serviceId, String protocolUid) { return mAccount.getServiceId() == serviceId && mAccount.getBuddyByProtocolUid(protocolUid) != null; } @Override public void onFilePicked(ActivityResult result, MainActivity activity) { if (result.getRequestCode() != ServiceUtils.getRequestCodeForActivity(mAccount.getEntityId().hashCode()) || result.getResultCode() != Activity.RESULT_OK) { return; } String filePath; if (mAwaitingUri != null) { filePath = mAwaitingUri.getPath(); mAwaitingUri = null; } else { filePath = result.getData().getData().getPath(); } File file = new File(filePath); if (!file.exists()) { Uri selectedImage = result.getData().getData(); String[] filePathColumn = {MediaStore.Images.Media.DATA}; Cursor cursor = activity.getContentResolver().query(selectedImage, filePathColumn, null, null, null); cursor.moveToFirst(); int columnIndex = cursor.getColumnIndex(filePathColumn[0]); filePath = cursor.getString(columnIndex); file = new File(filePath); cursor.close(); } if (!file.exists()) { Logger.log("No file found under path " + filePath, LoggerLevel.INFO); return; } Logger.log("File picked " + filePath, LoggerLevel.VERBOSE); try { activity.getCoreService().uploadAccountPhoto(mAccount.getServiceId(), filePath); } catch (RemoteException e) { activity.onRemoteException(e); } } @Override public boolean hasMenu(){ return true; } private void mergeBuddies(List<Buddy> buddies) { if (buddies == null || buddies.size() < 1) { return; } for (Buddy newb : buddies) { Buddy oldb = mAccount.getBuddyByProtocolUid(newb.getProtocolUid()); if (oldb != null) { if (oldb == newb) { //Shared VM for core and view, no need to merge entities at all break; } oldb.merge(newb); } } } protected ContactListAdapter getAdapter() { return mUpdater.getAdapter(); } protected abstract View onCreateContactListView(LayoutInflater inflater, ViewGroup group, Bundle saved); protected abstract Class<? extends ContactListAdapter> getContactListAdapterClassName(); }